package com.xian.im.common.exception;


import com.google.gson.JsonSyntaxException;
import com.google.gson.stream.MalformedJsonException;
import com.xian.im.common.exception.error.ApiError;
import com.xian.im.common.result.RestResult;
import com.xian.im.common.result.RestResultBuilder;
import com.xian.im.common.utils.sys.ThrowableUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.persistence.EntityNotFoundException;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.UnexpectedTypeException;
import java.nio.file.AccessDeniedException;
import java.rmi.ServerException;
import java.sql.SQLException;
import java.util.List;
import java.util.Set;

import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;


/**
 * Desc <p>Controller统一异常advice</p>
 *
 * @author xlr
 * @date 2017/7/3
 */
@RestControllerAdvice
@Slf4j
public class DefaultExceptionAdvice {


    /**
     * 处理所有不可知的异常
     */
    @ExceptionHandler(Throwable.class)
    public RestResult<ApiError> handleException(Throwable e) {
        // 打印堆栈信息
        log.error("请求地址：" + getRequestURI() + " " + ThrowableUtil.getStackTrace(e));
        return RestResultBuilder.builder().failure(ApiError.builder().message(e.getMessage())).code(1001).message("请联系服务端开发人员定位问题").build();
    }

    /**
     * MissingServletRequestParameterException 此异常后端代码严重bug。原因在httprequest请求完成后。发生再次使用Request获取参数值
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public RestResult badCredentialsException(MissingServletRequestParameterException e) {
        // 打印堆栈信息
        log.error("请求地址：" + getRequestURI() + " " + e.getMessage());
        return RestResultBuilder.builder().failure().code(1001).message("服务端异常,请联系开发人员").build();
    }

    /**
     * MethodArgumentTypeMismatchException  参数类型转换错误
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public RestResult badCredentialsException(MethodArgumentTypeMismatchException e) {
        // 打印堆栈信息
        String message = "参数类型转换错误，请检查参数类型是否正确。 " + e.getMessage();
        log.error("请求地址：" + getRequestURI() + message);
        return RestResultBuilder.builder().failure().code(1002).message(message).build();
    }

    /**
     * 访问不能访问资源，当前用户无权访问
     *
     * @param e
     * @return
     */
    @ExceptionHandler(AccessDeniedException.class)
    public RestResult badCredentialsException(AccessDeniedException e) {
        // 打印堆栈信息
        log.error("请求地址：" + getRequestURI() + e.getMessage());
        return RestResultBuilder.builder().failure().code(403).message("当前用户无权访问受保护的资源").build();
    }


    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    @ExceptionHandler(value = BadRequestException.class)
    public RestResult badRequestException(BadRequestException e) {
        // 打印堆栈信息
        log.error("请求地址：" + getRequestURI() + " " + ThrowableUtil.getStackTrace(e));
        return RestResultBuilder.builder().failure().code(GlobalErrorCode.UNAUTHORIZED.getCode()).message(GlobalErrorCode.UNAUTHORIZED.getMessage()).build();
    }

    /**
     * 处理 EntityExist
     */
    @ExceptionHandler(value = EntityExistException.class)
    public RestResult<ApiError> entityExistException(EntityExistException e) {
        // 打印堆栈信息
        log.error("请求地址：" + getRequestURI() + " " + ThrowableUtil.getStackTrace(e));
        return RestResultBuilder.builder().failure(ApiError.builder().message(e.getMessage())).code(1001).message("服务端异常,请联系开发人员").build();
    }

    /**
     * 数据库更新异常
     */
    @ExceptionHandler(value = DataIntegrityViolationException.class)
    public RestResult<ApiError> entityExistException(DataIntegrityViolationException e) {
        // 打印堆栈信息
        log.error("数据库更新异常，请求地址 {} 异常信息{}", getRequestURI(), ThrowableUtil.getStackTrace(e));
        return RestResultBuilder.builder().failure(ApiError.builder().message(e.getMessage())).code(1001).message("服务端异常,请联系开发人员").build();
    }

    /**
     * 处理 EntityNotFound
     */
    @ExceptionHandler(value = EntityNotFoundException.class)
    public RestResult<ApiError> entityNotFoundException(EntityNotFoundException e) {
        // 打印堆栈信息
        log.error("请求地址：" + getRequestURI() + " " + ThrowableUtil.getStackTrace(e));
        return RestResultBuilder.builder().failure(ApiError.builder().message(e.getMessage())).code(1001).message("服务端异常,请联系开发人员").build();
    }


//    @ExceptionHandler(value = MethodArgumentNotValidException.class)
//    public RestResult<ApiError> handleMethodArgumentNotValidException(MethodArgumentNotValidException exception) {
//        String message = "";
//        for (FieldError error : exception.getBindingRestResult().getFieldErrors()) {
////            error.getField()
////            error.getDefaultMessage()
//            message = error.getDefaultMessage();
//        }
//        log.info("请求地址：" + getRequestURI() + " " + "异常信息：{}", message);
//        return buildRestResult(ApiError.error(message));
//    }


    /**
     * validate 异常处理
     *
     * @return RestRestResult
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public RestResult<String> handleException(MethodArgumentNotValidException e) {
        String defaultMessage = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
        log.error("参数异常:{}", defaultMessage);
        return RestResultBuilder.builder().failure(ApiError.builder().message((defaultMessage)).status(1002).message("参数错误请检查参数是否正确")).build();
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({HttpMessageNotReadableException.class,})
    public RestResult handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
        log.error("请求地址：" + getRequestURI() + " " + "参数解析失败{}", e.getMessage());
        return RestResultBuilder.builder().failure(e.getHttpInputMessage()).code(1002).message("参数错误，参数解析失败").build();

    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({DuplicateKeyException.class,})
    public RestResult duplicateKeyException(DuplicateKeyException e) {
        log.error("请求地址：" + getRequestURI() + " " + "spring数据库索引异常{}", e.getMessage());
        return RestResultBuilder.builder().failure(e.getMessage()).code(1002).message("spring数据库索引异常").build();
    }

    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
    public RestResult handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
        log.error("不支持当前请求方法", e.getMessage());
        return RestResultBuilder.builder().failure().code(1002).message("不支持当前请求方法").build();
    }

    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
    @ExceptionHandler({HttpMediaTypeNotSupportedException.class})
    public RestResult handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e) {
        log.error("不支持当前媒体类型", e.getContentType());
        return RestResultBuilder.builder().failure().code(1002).message("不支持当前媒体类型").build();
    }

    @ResponseStatus(INTERNAL_SERVER_ERROR)
    @ExceptionHandler({SQLException.class})
    public RestResult handleSQLException(SQLException e) {
        log.error("服务运行SQLException异常", e);
        return RestResultBuilder.builder().failure(e.getMessage()).code(1001).message("服务端异常,请联系开发人员").build();
    }

    /**
     * 所有异常统一处理
     *
     * @return RestRestResult
     */
    @ResponseStatus(INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Exception.class)
    public RestResult handleException(Exception ex) {
        log.error("未知异常", ex);
        GlobalErrorCode error;
        String extMessage = null;
        if (ex instanceof BindException) {
            error = GlobalErrorCode.INVALID_PARAMETER;
            List<ObjectError> errors = ((BindException) ex).getAllErrors();
            if (errors.size() != 0) {
                StringBuilder msg = new StringBuilder();
                for (ObjectError objectError : errors) {
                    msg.append("Field error in object '").append(objectError.getObjectName()).append(" ");
                    if (objectError instanceof FieldError) {
                        msg.append("on field ").append(((FieldError) objectError).getField()).append(" ");
                    }
                    msg.append(objectError.getDefaultMessage()).append(" ");
                }
                extMessage = msg.toString();
            }
        } else if (ex instanceof MissingServletRequestParameterException) {
            error = GlobalErrorCode.INVALID_PARAMETER;
            extMessage = ex.getMessage();
        } else if (ex instanceof ConstraintViolationException) {
            error = GlobalErrorCode.INVALID_PARAMETER;
            Set<ConstraintViolation<?>> violations = ((ConstraintViolationException) ex).getConstraintViolations();
            final StringBuilder msg = new StringBuilder();
            for (ConstraintViolation<?> constraintViolation : violations) {
                msg.append(constraintViolation.getPropertyPath()).append(":").append(constraintViolation.getMessage()).append("\n");
            }
            extMessage = msg.toString();
        } else if (ex instanceof HttpMediaTypeNotSupportedException) {
            error = GlobalErrorCode.CONTENT_TYPE_NOT_SUPPORT;
            extMessage = ex.getMessage();
        } else if (ex instanceof HttpMessageNotReadableException) {
            error = GlobalErrorCode.INVALID_PARAMETER;
            extMessage = ex.getMessage();
        } else if (ex instanceof MethodArgumentNotValidException) {
            error = GlobalErrorCode.INVALID_PARAMETER;
            final BindingResult result = ((MethodArgumentNotValidException) ex).getBindingResult();
            if (result.hasErrors()) {
                extMessage = result.getAllErrors().get(0).getDefaultMessage();
            } else {
                extMessage = ex.getMessage();
            }
        } else if (ex instanceof HttpRequestMethodNotSupportedException) {
            error = GlobalErrorCode.METHOD_NOT_SUPPORTED;
            extMessage = ex.getMessage();
        } else if (ex instanceof UnexpectedTypeException) {
            error = GlobalErrorCode.INVALID_PARAMETER;
            extMessage = ex.getMessage();
        } else if (ex instanceof NoHandlerFoundException) {
            error = GlobalErrorCode.SERVICE_NOT_FOUND;
            extMessage = ex.getMessage();
        } else {
            error = GlobalErrorCode.SYSTEM_INTERNAL_ERROR;
            extMessage = ex.getMessage();
        }
        return RestResultBuilder.builder().failure(error).message(extMessage).build();
    }

    /**
     * BusinessException 业务异常处理
     *
     * @return RestRestResult
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(BusinessException.class)
    public RestResult handleException(BusinessException e) {e.printStackTrace();
        log.error("业务异常:", e);
        return RestResultBuilder.builder().failure().code(1003).message(e.getMessage()).build();
    }

    /**
     * BusinessException 业务异常处理
     *
     * @return RestRestResult
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(IllegalArgumentException.class)
    public RestResult handleException(IllegalArgumentException e) {
        log.error("业务异常:{}", e.getMessage());
        return RestResultBuilder.builder().failure().code(1003).message(e.getMessage()).build();
    }


    /**
     * BusinessException 业务异常处理
     *
     * @return RestRestResult
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(ValidateCodeException.class)
    public RestResult handleException(ValidateCodeException e) {
        log.error("请求参数校验异常:{}", e.getMessage());
        return RestResultBuilder.builder().failure().code(1002).message(e.getMessage()).build();
    }

    /**
     * InvalidParamException 参数校验异常
     *
     * @return RestRestResult
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(InvalidParamException.class)
    public RestResult handleException(InvalidParamException e) {
        log.error("参数验证失败:{}", e.getMessage());
        return RestResultBuilder.builder().failure().code(1002).message(e.getMessage()).build();
    }

    /**
     * ClientException 客户端异常 给调用者 app,移动端调用
     *
     * @return RestRestResult
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(ClientException.class)
    public RestResult handleException(ClientException e) {
        log.error("客户端异常:{}", e.getMessage());
        return RestResultBuilder.builder().failure().code(1002).message(e.getMessage()).build();
    }

    /**
     * ServerException 服务端异常, 微服务服务端产生的异常
     *
     * @return RestRestResult
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(ServerException.class)
    public RestResult handleException(ServerException e) {
        log.error("ServerException 服务端异常{}", e.getMessage());
        return RestResultBuilder.builder().failure().code(1001).message(e.getMessage()).build();
    }

    /**
     * gson 解析异常。
     *
     * @return RestRestResult
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(JsonSyntaxException.class)
    public RestResult handleException(JsonSyntaxException e) {
        log.error("gson 解析异常", e);
        return RestResultBuilder.builder().failure().code(1001).message(e.getMessage()).build();
    }

    /**
     * sql 异常。
     *
     * @return RestRestResult
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(BadSqlGrammarException.class)
    public RestResult handleException(BadSqlGrammarException e) {
        log.error("sql 异常{}", e);
        return RestResultBuilder.builder().failure().code(1001).message(e.getMessage()).build();
    }


    /**
     * json解析 异常。
     *
     * @return RestRestResult
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(MalformedJsonException.class)
    public RestResult handleException(MalformedJsonException e) {
        log.error("json解析异常{}", e.getMessage());
        return RestResultBuilder.builder().failure().code(1001).message("json解析异常").build();
    }

    private String getRequestURI() {
        try {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            if (request != null) {
                return request.getRequestURI();
            }
        } catch (Exception e) {
            log.warn("获取异常的请求url失败");
        }
        return null;
    }
}
